/**
 * 
 */
package automaton;

import helpers.AutomataOperations;
import helpers.FileOperations;
import helpers.HandleHashSet;
import interfaces.Automaton;
import interfaces.State;
import interfaces.Transition;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import exceptions.NotDeterministInitalStateException;
import exceptions.NotDeterministTransitionException;
import exceptions.UnknownInitialStateException;
import exceptions.UnknownTerminalStateException;

/**
 * @author LE VAN LINH
 *
 */
public class DFA implements Automaton {
	int numTrans;
	
	public int getNumTrans() {
		return numTrans;
	}

	public void setNumTrans(int numTrans) {
		this.numTrans = numTrans;
	}

	State initialState;
	private final Map<State,Map<String,Transition<String>>> automaton;
	
	/**
	 * Constructor of automaton
	 * @param transitions
	 * @throws UnknownInitialStateException
	 * @throws NotDeterministInitalStateException
	 * @throws NotDeterministTransitionException
	 */
	@SuppressWarnings("unchecked")
	public DFA(Transition<String>... transitions) throws UnknownInitialStateException, NotDeterministInitalStateException, NotDeterministTransitionException{
		 this.automaton = new HashMap<State, Map<String,Transition<String>>>();
		 for (Transition<String> transition : transitions) {
			if(getTransition(transition.source(), transition.label())!=null)
					throw new NotDeterministTransitionException(transition);			
				
			addState(transition.source());//add source of automata
			Map<String, Transition<String>> t = automaton.get(transition.source());
			if(transition.label() != null)
				t.put(transition.label(), transition);// put the transition
		}
		 if(initialState==null)
			 throw new UnknownInitialStateException(this);
	}
	
	public DFA()
	{
		automaton = new HashMap<>();
	}
	
	/**
	 * Add the new state into automaton
	 * @param s
	 * @throws NotDeterministInitalStateException
	 */
	public final void addState(State s) throws NotDeterministInitalStateException{
		if(automaton!=null){
			if(!automaton.containsKey(s)){
				automaton.put(s, new HashMap<String,Transition<String>>());
				if (s.initial()) {
					if (initialState == null) {
						initialState = s;
					}
					else{				
						if(!initialState.equals(s))
							throw new NotDeterministInitalStateException(this);
					}
				}
			}
		}
	}
	
	/**
	 * Get the transition with pair (source, label)
	 * @param source
	 * @param label
	 * @return
	 */
	public Transition<String> getTransition(State source, Object label){		
		try{
			if(!automaton.containsKey(source))
				throw new NoSuchElementException();
			return automaton.get(source).get(label);
		}
		catch(Exception e){
			return null;
		}
	}
	
	/**
	 * Get the DFA
	 * @return
	 */
	public Map<State,Map<String,Transition<String>>> getDFA(){
		return automaton;
	}
	
	/**
	 * Print the automaton in console
	 */
	public void printString() {
		for(Map.Entry<State,Map<String,Transition<String>>> entry: automaton.entrySet()){			
			//System.out.print(entry.getKey().label());
			for(Map.Entry<String, Transition<String>> t : entry.getValue().entrySet()){
				if(t.getKey() != null && t.getValue().target() != null)
					System.out.println(entry.getKey().label() + " --> " + t.getKey() + " --> " + t.getValue().target().label());				
			}			
			//System.out.println("");
		}
		System.out.println("Initial state: " + initialState.label());
	}

	/**
	 * Get set of states which reach to target with label
	 * @param target
	 * @param label
	 * @return
	 */
	public HandleHashSet getSet_StateSource(State target, Object label){
		HandleHashSet list = new HandleHashSet();
		try{					
			Iterator<Map<String, Transition<String>>> valuesIterators = automaton.values().iterator();
			while(valuesIterators.hasNext()){
				Map<String, Transition<String>> value = valuesIterators.next();
				if(value!=null){
					if(value.get(label) != null  && label.toString().compareTo("") != 0
							&& value.get(label).target().equals(target))
						list.add(value.get(label).source());
				}
			}			
			return list;
		}
		catch(Exception e){
			return null;
		}
	}
	
	public String saveToFile()
	{
		String rs = "";
		rs += "//Finite Automata\n";
		
		for(Map.Entry<State,Map<String,Transition<String>>> entry: automaton.entrySet())
		{
			int i=0;
			rs+=entry.getKey().label() + "\t|";
			
			if (entry.getKey().initial())
				rs+=" initial\t|";
			else
				rs+=" |";
			
			if (entry.getKey().terminal())
				rs+=" terminal\t| ";
			else
				rs+=" |";
			
			
			for(Map.Entry<String, Transition<String>> t : entry.getValue().entrySet())
			{
				if (i > 0)
					rs+=",\t";
				rs+= t.getKey() + ":" + t.getValue().target().label();
				i++;
			}		
			
			rs+="\n";
		}
		return rs;
		//FileOperations.saveFile("data/10000_10_"+number+".txt", rs);
	}

	/**
	 * Get all transitions of automata
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Transition<String>[] getTransitions() {
		List<Transition<String>> list = new ArrayList<Transition<String>>();				
		for(Map.Entry<State,Map<String,Transition<String>>> entry: automaton.entrySet()){
			//list.addAll((Collection<? extends TransitionImpl<String>>) entry.getValue().values());
			for(Map.Entry<String, Transition<String>> t : entry.getValue().entrySet()){
				if(t.getKey() != null && t.getValue().target() != null)
					list.add(t.getValue());				
			}
		}
		Transition<String>[] result = new TransitionImpl[list.size()];
		list.toArray(result);
		return result;
	}

	/**
	 * Get all initial states of automata
	 */
	@Override
	public List<State> getInitStates() {
		List<State> listStates = new ArrayList<State>();
		for(Map.Entry<State,Map<String,Transition<String>>> entry: automaton.entrySet()){
			if(entry.getKey().initial())
				listStates.add(entry.getKey());
		}
		return listStates;
	}

	/**
	 * Get all terminal states of automata
	 */
	@Override
	public List<State> getTermStates() {
		List<State> listStates = new ArrayList<State>();
		for(Map.Entry<State,Map<String,Transition<String>>> entry: automaton.entrySet()){
			if(entry.getKey().terminal())
				listStates.add(entry.getKey());
		}
		return listStates;
	}

	@Override
	public Map<State, Map<String, List<Transition<String>>>> getFA() throws UnknownInitialStateException, UnknownTerminalStateException {
		FA finiteAutomata = new FA(getTransitions());
		
		return finiteAutomata.getFA();
	}

	@Override
	public Automaton reduceInitState() {
		return this;
	}

	@Override
	public List<String> getLabels() {
		HashSet<String> listLabels =  AutomataOperations.getLabels(this);
		List<String> result = new ArrayList<String>(listLabels);
		return result;
	}

	@Override
	public void reverseStates() {
		for (State s: automaton.keySet())
			if(s instanceof StateImpl)
				((StateImpl) s).reversed();
		
	}
}
